package com.jxtc.enterprise.common.aspectj;

import com.jxtc.enterprise.admin.vo.adminEndpoint.AdminTenantDecryptdTokenVO;
import com.jxtc.enterprise.common.exception.RoleVerificationFailureException;
import com.jxtc.enterprise.common.exception.TokenMissingException;
import com.jxtc.enterprise.common.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
@Slf4j
public class TokenAspect {

    @Value("${env.profile}")
    private String envProfile;

    private final RedisTemplate<String, Object> redisTemplate;

    public TokenAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 定义切入点，匹配所有带有 @TokenRequired 注解的方法 或 @TokenRequired 标记的类中的所有方法
     */
    @Pointcut("@annotation(com.jxtc.enterprise.common.aspectj.TokenRequired) || @within(com.jxtc.enterprise.common.aspectj.TokenRequired)")
    public void tokenRequiredMethods() {
    }


    @Before("tokenRequiredMethods()")
    public void checkToken(JoinPoint joinPoint) throws Exception {
        // 获取当前请求的 HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            // 从 header 中获取 token
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("Token");
            // 添加日志方便排查哪个接口出现 token 验证问题
            String requestURI = request.getRequestURI();
            log.info("Checking token for request URI: {}", requestURI);

            // 校验 token 是否存在
            if (token == null || token.isEmpty()) {
                log.error("Token is missing for request URI: {}, token: {}", requestURI, token);
                throw new TokenMissingException("Token 缺失");
            }

            // 验证 token 逻辑
            if (!validateToken(token)) {
                log.error("Token validation failed for request URI: {}", requestURI);
                throw new RoleVerificationFailureException("Token 验证失败");
            }

            // 获取 TokenRequired 注解并提取 role 字段
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            TokenRequired tokenRequired = signature.getMethod().getAnnotation(TokenRequired.class);
            if (tokenRequired == null) {
                // 如果方法上没有标记该注解，说明该注解标记在当前方法所在的类上
                tokenRequired = joinPoint.getTarget().getClass().getAnnotation(TokenRequired.class);
            }

            if (tokenRequired != null) {
                // 获取注解中要求的用户角色
                String requiredRole = tokenRequired.role();
                // 从 token 中解析出该用户角色
                AdminTenantDecryptdTokenVO tokenVO = JwtUtil.decryptPrincipalFromToken(token);
                String userRole = tokenVO.getRole();
                // 如果当前用户的角色不符合 TokenRequired 注解要求的角色，返回 403 状态码
                if (!requiredRole.isEmpty() && !requiredRole.equalsIgnoreCase(userRole)) {
                    log.error("The role is insufficient for request URI: {}, user role: '{}', required role: '{}'", requestURI, userRole, requiredRole);
                    throw new RoleVerificationFailureException("用户权限不足");
                }
                // 很多接口中都会使用到userId，当token有效性和 角色权限校验通过后，将解析出的 userId放入到 request的 attribute 中，后续接口中直接从 attribute中取出来用，不用在各个接口中重复写代码 对token进行解析
                request.setAttribute("userId", tokenVO.getUserId());
            }
        }
    }

    /**
     * 校验逻辑，登录成功后，在redis中新增一个数据项，key为 租户或管理员id，value 为token，有效期在 application.properties 中配置，默认值为 30 天
     * 每次接口调用时，从token中解析出 userId作为key，查询出redis中的数据项，得到token，比对下
     * @param token
     * @return
     */
    private boolean validateToken(String token) {

        if (envProfile.equalsIgnoreCase("test")) {
            return true;
        }
        AdminTenantDecryptdTokenVO tokenVO = JwtUtil.decryptPrincipalFromToken(token);
        // 以用户id为key，从 Redis 查询token，若 用户id对应的token存在，且token 与接口header中的一致，则表示token验证通过，否则验证不通过
        String tokenInRedis = (String)redisTemplate.opsForValue().get(tokenVO.getUserId());
        if (StringUtils.isNotEmpty(tokenInRedis) && token.equalsIgnoreCase(tokenInRedis)) {
            return true;
        } else {
            return false;
        }
    }

}